package com.kalvan.web.log;

import com.alibaba.fastjson.JSON;
import com.kalvan.core.system.SystemUtil;
import com.kalvan.core.utils.Sysdate;
import com.kalvan.remoteserver.bean.req.LogDto;
import com.kalvan.remoteserver.iface.IBaseRemote;
import com.kalvan.web.log.annotation.SysLog;
import com.kalvan.web.servlet.interceptor.CallInterfaceInterceptor;
import com.kalvan.web.utils.RequestUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.rpc.cluster.support.FailsafeCluster;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author chenliang
 */
@Aspect
@Component
@Slf4j
public class LogAopAction {
    /**
     * 操作前原始信息线程存储
     */
    private static final ThreadLocal<String> oldDetailThreadLocal = new ThreadLocal<String>();
    /**
     * 操作后信息线程存储
     */
    private static final ThreadLocal<String> newDetailThreadLocal = new ThreadLocal<String>();

    /**
     * 操作后信息线程存储
     */
    private static final ThreadLocal<LogDto> logThreadLocal = new ThreadLocal<LogDto>();

    /**
     * 操作类型
     */
    private static final String LOG_GROUP = "group";
    /**
     * 操作类型
     */
    private static final String LOG_TYPE = "type";
    /**
     * 操作地址
     */
    private static final String LOG_ACTION = "action";
    /**
     * 操作说明
     */
    private static final String LOG_REMARK = "remark";
    /**
     * 被操作者抓取的参数
     */
    private static final String LOG_OPERATED = "operated";
    /**
     * 需要抓取的参数
     */
    private static final String LOG_PARAMS = "params";
    /**
     * 需要抓取所有参数
     */
    private static final String LOG_PARAMS_ALL = "*";

    /**
     * dubbo 基础服务
     */
    @Reference(cluster = FailsafeCluster.NAME)
    private IBaseRemote baseRemote;

    @Pointcut("@annotation(com.kalvan.web.log.annotation.SysLog)")
    private void pointCutMethod() {
    }

    /**
     * 记录操作日志
     */
    @After("pointCutMethod()")
    public void recordLog(JoinPoint joinPoint) {
        LogDto logDto = getLog();
        try {
            if (logDto == null) {
                logDto = initLogDto(joinPoint);
            }
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            logDto.setLogUser(CallInterfaceInterceptor.getUserName());
            logDto.setSystemCode(SystemUtil.constants.getCode());
            logDto.setLogIp(RequestUtils.getIpAddr(request));
            logDto.setLogTime(Sysdate.getDate());
            baseRemote.insertLog(logDto);
        } catch (Exception e) {
            log.error("插入日志异常:" + JSON.toJSONString(logDto), e);
        }
    }

    /**
     * 从方法注解中获取LogDto对象
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    private LogDto initLogDto(JoinPoint joinPoint) throws Exception {
        LogDto logDto = new LogDto();
        Map<String, Object> params = showParams();
        // 下面开始获取 Action Type Remark
        Map<String, Object> map = getLogMark(joinPoint);
        logDto.setLogGroup(map.get(LOG_GROUP).toString());
        logDto.setLogType((Integer) map.get(LOG_TYPE));
        logDto.setLogAction(map.get(LOG_ACTION).toString());
        logDto.setLogRemark(map.get(LOG_REMARK).toString());
        // 获取操作明细
        if (StringUtils.isNotBlank(map.get(LOG_OPERATED).toString())) {
            logDto.setLogOperated((String) params.get(map.get(LOG_OPERATED)));
        }
        logDto.setLogOldDetail(getOldDetail());
        logDto.setLogDetail(getNewDetail());
        return logDto;
    }

    /**
     * 获取注解信息
     *
     * @param joinPoint
     * @return
     * @see
     */
    private Map<String, Object> getLogMark(JoinPoint joinPoint) throws Exception {
        Map<String, Object> map = new HashMap<>();
        // 目标类全路径名
        String targetName = joinPoint.getTarget().getClass().getName();
        // 反射得到目标类
        Class<?> targetClass = Class.forName(targetName);
        // 目标方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 目标方法名（正在访问的方法）
        String methodName = methodSignature.getName();
        // 方法参数：数组类型
        Object[] arguments = joinPoint.getArgs();
        //方法上的注解内容提取
        SysLog sysLog = method.getAnnotation(SysLog.class);
        if (sysLog != null) {
            map.put(LOG_GROUP, sysLog.group());
            map.put(LOG_TYPE, sysLog.type().getCode());
            map.put(LOG_ACTION, sysLog.action());
            map.put(LOG_REMARK, sysLog.remark());
///          map.put(LOG_PARAMS, logAnnotation.params());
            map.put(LOG_OPERATED, sysLog.operated());
        } else {
            throw new Exception("日志注解获取异常" + methodName);
        }
        //从request里获取
        if (map.get(LOG_ACTION) == null || StringUtils.isBlank(map.get(LOG_ACTION).toString())) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            map.put(LOG_ACTION, request.getRequestURI());
        }
        // 可使用@ApiOperation来替代remark
        if (map.get(LOG_REMARK) == null || StringUtils.isBlank(map.get(LOG_REMARK).toString())) {
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            if (apiOperation != null) {
                map.put(LOG_REMARK, apiOperation.value());
            }
        }
        // 获取类上的日志类型注解
        SysLog logGroup = targetClass.getAnnotation(SysLog.class);
        if (logGroup != null) {
            if (map.get(LOG_GROUP) == null || StringUtils.isBlank(map.get(LOG_GROUP).toString())) {
                map.put(LOG_GROUP, logGroup.group());
            }
        }
        // 检查是否正确使用
        if (map.get(LOG_GROUP) == null || StringUtils.isBlank(map.get(LOG_GROUP).toString())) {
            throw new Exception("日志组不能为空");
        }
        return map;
    }

    /**
     * 获取请求参数
     *
     * @return
     * @see
     */
    private Map<String, Object> showParams() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        Map<String, Object> map = new HashMap<String, Object>();
        Enumeration<?> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            String[] paramValues = request.getParameterValues(paramName);
            if (paramValues.length == 1) {
                String paramValue = paramValues[0];
                if (paramValue.length() != 0) {
                    map.put(paramName, paramValue);
                }
            }
        }
        return map;
    }


    /**
     * 更新前数据
     *
     * @return
     */
    public String getOldDetail() {
        try {
            String oldDetail = oldDetailThreadLocal.get();
            if (StringUtils.isNotBlank(oldDetail)) {
                oldDetailThreadLocal.remove();
                return oldDetail;
            }
        } catch (Throwable t) {
            log.error("oldDetailThreadLocal处理异常", t);
        }
        return "";
    }

    /**
     * 更新后数据
     *
     * @return
     */
    private static String getNewDetail() {
        try {
            String newDetail = newDetailThreadLocal.get();
            if (StringUtils.isNotBlank(newDetail)) {
                newDetailThreadLocal.remove();
                return newDetail;
            }
        } catch (Throwable t) {
            log.error("newDetailThreadLocal处理异常", t);
        }
        return "";
    }

    /**
     * 更新后数据
     *
     * @return
     */
    private static LogDto getLog() {
        try {
            LogDto logDto = logThreadLocal.get();
            logThreadLocal.remove();
            return logDto;
        } catch (Throwable t) {
            log.error("logThreadLocal处理异常", t);
        }
        return null;
    }

    /**
     * 设置数据更新后的对象,用来记录日志
     *
     * @param obj
     * @see
     */
    public static void setNewDetail(Object obj) {
        try {
            newDetailThreadLocal.set(JSON.toJSONString(obj));
        } catch (Exception e) {
            log.error("newDetailThreadLocal处理异常", e);
        }
    }

    /**
     * 设置数据更新前的对象,用来记录日志
     *
     * @param obj
     * @see
     */
    public static void setOldDetail(Object obj) {
        try {
            oldDetailThreadLocal.set(JSON.toJSONString(obj));
        } catch (Exception e) {
            log.error("oldDetailThreadLocal处理异常", e);
        }
    }

    /**
     * 设置数据更新前的对象,用来记录日志
     *
     * @param logDto
     * @see
     */
    public static void setLog(LogDto logDto) {
        try {
            logThreadLocal.set(logDto);
        } catch (Exception e) {
            log.error("logThreadLocal处理异常", e);
        }
    }
}
